/*
 * File     : PlatformUtilities.java
 * Created  : 16 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.platform;

import java.nio.IntBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import com.googlecode.dni.internal.MemoryAccess;
import com.googlecode.dni.platform.LinuxPlatformType;
import com.googlecode.dni.platform.PlatformGroup;
import com.googlecode.dni.platform.PlatformType;
import com.googlecode.dni.platform.WindowsPlatformType;
import com.googlecode.dni.type.Pointer;
import com.googlecode.dni.type.nativetype.NativeChar;
import com.googlecode.dni.type.nativetype.NativeInt;
import com.googlecode.dni.type.nativetype.NativeLong;
import com.googlecode.dni.type.nativetype.NativeShort;
import com.googlecode.dni.type.nativetype.SizeT;
import com.googlecode.dni.type.nativetype.WCharT;


/**
 * Provides utilities for {@link Platform} implementations.
 *
 * @author Matthew Wilson
 */
public final class PlatformUtilities
{

    /** Prevents instantiation. */
    private PlatformUtilities()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * Creates a default {@link Platform}.
     *
     * @return the platform
     */
    public static Platform getPlatform()
    {
        PlatformGroup group = getPlatformGroup();
        PlatformType type = getPlatformType();
        switch ( group )
        {
            case LINUX:
                return getLinuxPlatform( (LinuxPlatformType) type );

            case WINDOWS:
                return getWindowsPlatform( (WindowsPlatformType) type );

            default:
                throw new AssertionError( "Unsupported platform group:" + group );
        }
    }

    /**
     * Obtains <code>sun.misc.Unsafe</code> by nefarious means.
     *
     * @return the unsafe object, or <code>null</code>
     */
    public static native Object getSunMiscUnsafe();


    /** Flag to set to make a page executable. */
    static final int PAGE_EXECTUABLE = 1;

    /** Flag to set to make a page writable. */
    static final int PAGE_WRITABLE = 2;

    /**
     * Determines the native sizes using JNI.
     *
     * @return native sizes
     *
     * @see Platform#getNativeSizes()
     */
    static Map< Class< ? >, Integer > getNativeSizes()
    {
        Map< Class< ? >, Integer > nativeSizes = new HashMap< Class< ? >, Integer >();

        Pointer pointer = MemoryAccess.ALLOCATOR.allocate( 256 );
        try
        {
            getNativeSizesImpl( pointer.address() );

            IntBuffer buffer = pointer.getBuffer( 256 ).asIntBuffer();

            nativeSizes.put( NativeChar.class, buffer.get() );
            nativeSizes.put( NativeShort.class, buffer.get() );
            nativeSizes.put( NativeInt.class, buffer.get() );
            nativeSizes.put( NativeLong.class, buffer.get() );
            nativeSizes.put( WCharT.class, buffer.get() );
            nativeSizes.put( SizeT.class, buffer.get() );
            nativeSizes.put( Pointer.class, buffer.get() );

            assert buffer.get() == 0;
            assert !nativeSizes.values().contains( 0 );
        }
        finally
        {
            MemoryAccess.ALLOCATOR.free( pointer );
        }

        return Collections.unmodifiableMap( nativeSizes );
    }

    /**
     * Obtains the default charset; not implemented on all platforms.
     *
     * @return the default charset, or some locale information;
     *         result depends on the platform
     */
    static native String getNativeLocale();

    /**
     * @return the page size in bytes
     */
    static native int getPageSize();

    /**
     * @param size
     *            the size in bytes
     * @param flags
     *            the flags
     * @return a pointer
     */
    static native long allocatePages( int size, int flags );

    /**
     * @param pointer
     *            the pointer
     * @param size
     *            the size in bytes
     * @param flags
     *            the flags
     * @return zero on success; error code otherwise
     */
    static native int protectPages( long pointer, int size, int flags );

    /**
     * @param pointer
     *            the pointer
     * @param size
     *            the size
     * @return zero on success; error code otherwise
     */
    static native int unallocatePages( long pointer, int size );

    /**
     * @param type
     *            the type
     * @return the platform
     */
    private static Platform getLinuxPlatform( final LinuxPlatformType type )
    {
        switch ( type )
        {
            case X86:
                return new LinuxX86Platform();

            case X86_64:
                return new LinuxX86_64Platform();

            default:
                throw new AssertionError( "Unsupported platform type:" + type );
        }
    }

    /**
     * @param type
     *            the type
     * @return the platform
     */
    private static Platform getWindowsPlatform( final WindowsPlatformType type )
    {
        switch ( type )
        {
            case X86:
                return new WindowsX86Platform();

            case X64:
                return new WindowsX64Platform();

            default:
                throw new AssertionError( "Unsupported platform type:" + type );
        }
    }

    /**
     * @return the platform group
     */
    private static native PlatformGroup getPlatformGroup();

    /**
     * @return the platform type
     */
    private static native PlatformType getPlatformType();

    /**
     * Writes the sizes of the native types into the given buffer.
     *
     * @param destAddress
     *            the destination address
     */
    private static native void getNativeSizesImpl( long destAddress );

}

